1mod traits;
4pub use traits::SceneItemExtSceneTrait;
5
6use std::{
7 fmt::Debug,
8 hash::Hash,
9 sync::{Arc, Mutex},
10};
11
12use libobs::{obs_scene_item, obs_transform_info, obs_video_info};
13use num_traits::FromPrimitive;
14
15use crate::{
16 enums::{
17 ObsAlignment, ObsBlendMethod, ObsBlendMode, ObsBoundsType, ObsOrderMovement, ObsScaleType,
18 },
19 graphics::Vec2,
20 impl_obs_drop,
21 macros::trait_with_optional_send_sync,
22 run_with_obs,
23 runtime::ObsRuntime,
24 scenes::{ObsSceneRef, ObsTransformInfo, ObsTransformInfoBuilder},
25 sources::ObsSourceTrait,
26 unsafe_send::{Sendable, SmartPointerSendable},
27 utils::{ObsDropGuard, ObsError},
28};
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
35pub struct ObsSceneItemCrop {
36 pub left: i32,
37 pub top: i32,
38 pub right: i32,
39 pub bottom: i32,
40}
41
42impl ObsSceneItemCrop {
43 pub const fn new(left: i32, top: i32, right: i32, bottom: i32) -> Self {
44 Self {
45 left,
46 top,
47 right,
48 bottom,
49 }
50 }
51}
52
53impl From<ObsSceneItemCrop> for libobs::obs_sceneitem_crop {
54 fn from(value: ObsSceneItemCrop) -> Self {
55 Self {
56 left: value.left,
57 top: value.top,
58 right: value.right,
59 bottom: value.bottom,
60 }
61 }
62}
63
64impl From<libobs::obs_sceneitem_crop> for ObsSceneItemCrop {
65 fn from(value: libobs::obs_sceneitem_crop) -> Self {
66 Self::new(value.left, value.top, value.right, value.bottom)
67 }
68}
69
70#[derive(Debug, Clone, PartialEq)]
73pub struct ObsSceneItemTransformSnapshot {
74 pub position: Vec2,
75 pub scale: Vec2,
76 pub alignment: ObsAlignment,
77 pub rotation: f32,
78 pub bounds: Vec2,
79 pub bounds_type: ObsBoundsType,
80 pub bounds_alignment: ObsAlignment,
81 pub crop_to_bounds: bool,
82 pub crop: ObsSceneItemCrop,
83 pub scale_filter: ObsScaleType,
84 pub blend_method: ObsBlendMethod,
85 pub blend_mode: ObsBlendMode,
86}
87
88#[derive(Debug, Clone, PartialEq)]
90pub struct ObsSceneItemStateSnapshot {
91 pub transform: ObsSceneItemTransformSnapshot,
92 pub visible: bool,
93 pub locked: bool,
94 pub order_position: i32,
95}
96
97#[derive(Debug)]
98pub(super) struct _ObsSceneItemDropGuard {
99 scene_item: Sendable<*mut obs_scene_item>,
100 removed: Arc<Mutex<bool>>,
101 runtime: ObsRuntime,
102}
103
104impl ObsDropGuard for _ObsSceneItemDropGuard {}
105impl_obs_drop!(
106 _ObsSceneItemDropGuard,
107 (scene_item, removed),
108 move || unsafe {
109 let mut removed = removed
112 .lock()
113 .unwrap_or_else(|poisoned| poisoned.into_inner());
114 if !*removed {
115 libobs::obs_sceneitem_remove(scene_item.0);
116 *removed = true;
117 }
118 libobs::obs_sceneitem_release(scene_item.0);
119 }
120);
121
122pub(super) fn wrap_owned_scene_item_ref(
125 scene_item: Sendable<*mut obs_scene_item>,
126 runtime: ObsRuntime,
127) -> (SmartPointerSendable<*mut obs_scene_item>, Arc<Mutex<bool>>) {
128 let removed = Arc::new(Mutex::new(false));
129 let drop_guard = _ObsSceneItemDropGuard {
130 scene_item: scene_item.clone(),
131 removed: removed.clone(),
132 runtime: runtime.clone(),
133 };
134 let handle = SmartPointerSendable::new(
135 scene_item.0,
136 Arc::new(drop_guard),
137 runtime.native_registry(),
138 );
139 (handle, removed)
140}
141
142pub(super) fn scene_item_native_handle<T: SceneItemTrait + ?Sized>(
143 item: &T,
144) -> SmartPointerSendable<*mut obs_scene_item> {
145 item.__native_handle().clone()
146}
147
148pub(super) fn mark_scene_item_removed<T: SceneItemTrait + ?Sized>(item: &T) {
149 let mut removed = item
150 .__removed_flag()
151 .lock()
152 .unwrap_or_else(|poisoned| poisoned.into_inner());
153 *removed = true;
154}
155
156#[derive(Debug, Clone)]
157pub struct ObsSceneItemRef<T: ObsSourceTrait + Clone> {
161 scene_item_ptr: SmartPointerSendable<*mut obs_scene_item>,
163 removed: Arc<Mutex<bool>>,
164 runtime: ObsRuntime,
165 _scene_ptr: SmartPointerSendable<*mut libobs::obs_scene>,
172
173 underlying_source: T,
175}
176
177#[derive(Debug, Clone)]
180pub struct ObsSceneItemHandle {
181 scene_item_ptr: SmartPointerSendable<*mut obs_scene_item>,
182 removed: Arc<Mutex<bool>>,
183 runtime: ObsRuntime,
184 _scene_ptr: SmartPointerSendable<*mut libobs::obs_scene_t>,
185}
186
187impl ObsSceneItemHandle {
188 pub(super) fn from_owned_native_ref(
189 item: Sendable<*mut obs_scene_item>,
190 scene_ptr: SmartPointerSendable<*mut libobs::obs_scene_t>,
191 runtime: ObsRuntime,
192 ) -> Self {
193 let (scene_item_ptr, removed) = wrap_owned_scene_item_ref(item, runtime.clone());
194 Self {
195 scene_item_ptr,
196 removed,
197 runtime,
198 _scene_ptr: scene_ptr,
199 }
200 }
201}
202
203impl<T: ObsSourceTrait + Clone> ObsSceneItemRef<T> {
204 pub(crate) fn new(
205 scene: &ObsSceneRef,
206 source: T,
207 runtime: ObsRuntime,
208 ) -> Result<Self, ObsError> {
209 runtime.ensure_same_runtime(source.runtime())?;
210 runtime.ensure_same_runtime(&scene.runtime)?;
211 let scene_ptr = scene.as_ptr();
212 let source_ptr = source.__native_handle();
213
214 let scene_item_ptr = run_with_obs!(runtime, (scene_ptr, source_ptr), move || {
215 let ptr = unsafe {
216 libobs::obs_scene_add(scene_ptr.get_ptr(), source_ptr.get_ptr())
218 };
219
220 if ptr.is_null() {
221 Err(ObsError::NullPointer(None))
222 } else {
223 unsafe {
224 libobs::obs_sceneitem_addref(ptr);
227 }
228 Ok(Sendable(ptr))
229 }
230 })??;
231
232 let removed = Arc::new(Mutex::new(false));
233 let drop_guard = _ObsSceneItemDropGuard {
234 scene_item: scene_item_ptr.clone(),
235 removed: removed.clone(),
236 runtime: runtime.clone(),
237 };
238
239 let scene_item_ptr = SmartPointerSendable::new(
240 scene_item_ptr.0,
241 Arc::new(drop_guard),
242 runtime.native_registry(),
243 );
244
245 Ok(Self {
246 underlying_source: source,
247 _scene_ptr: scene.as_ptr().clone(),
248 scene_item_ptr,
249 removed,
250 runtime,
251 })
252 }
253}
254
255trait_with_optional_send_sync! {
256 pub(crate) trait SceneItemTraitSealed: Debug {
257 fn __native_handle(&self) -> &SmartPointerSendable<*mut obs_scene_item>;
258 fn __removed_flag(&self) -> &Arc<Mutex<bool>>;
259 }
260}
261
262trait_with_optional_send_sync! {
263 #[allow(private_bounds)]
264 pub trait SceneItemTrait: SceneItemTraitSealed {
265 fn runtime(&self) -> ObsRuntime;
266
267 fn object_id(&self) -> crate::unsafe_send::NativeObjectId {
268 self.__native_handle().native_id()
269 }
270
271 fn remove_from_scene(&self) -> Result<(), ObsError> {
274 let mut removed = self
275 .__removed_flag()
276 .lock()
277 .unwrap_or_else(|poisoned| poisoned.into_inner());
278 if *removed {
279 return Ok(());
280 }
281 let self_ptr = self.__native_handle().clone();
282 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
283 libobs::obs_sceneitem_remove(self_ptr.get_ptr());
286 })?;
287 *removed = true;
288 Ok(())
289 }
290
291 fn is_removed(&self) -> bool {
293 *self
294 .__removed_flag()
295 .lock()
296 .unwrap_or_else(|poisoned| poisoned.into_inner())
297 }
298
299 fn get_transform_info(&self) -> Result<ObsTransformInfo, ObsError> {
301 let self_ptr = self.__native_handle().clone();
302 let item_info = run_with_obs!(self.runtime(), (self_ptr), move || {
303 let mut item_info: obs_transform_info = unsafe {
304 std::mem::zeroed()
306 };
307 unsafe {
308 libobs::obs_sceneitem_get_info2(self_ptr.get_ptr(), &mut item_info)
310 };
311
312 ObsTransformInfo(item_info)
313 })?;
314
315 Ok(item_info)
316 }
317
318 fn get_source_position(&self) -> Result<Vec2, ObsError> {
320 let self_ptr = self.__native_handle().clone();
321 let position = run_with_obs!(self.runtime(), (self_ptr), move || {
322 let main_pos = unsafe {
323 let mut main_pos: libobs::vec2 = std::mem::zeroed();
325
326 libobs::obs_sceneitem_get_pos(self_ptr.get_ptr(), &mut main_pos);
328
329 main_pos
330 };
331
332 Vec2::from(main_pos)
333 })?;
334
335 Ok(position)
336 }
337
338 fn get_source_scale(&self) -> Result<Vec2, ObsError> {
340 let self_ptr = self.__native_handle().clone();
341 let scale = run_with_obs!(self.runtime(), (self_ptr), move || {
342 let main_pos = unsafe {
343 let mut main_pos: libobs::vec2 = std::mem::zeroed();
345
346 libobs::obs_sceneitem_get_scale(self_ptr.get_ptr(), &mut main_pos);
348
349 main_pos
350 };
351
352 Vec2::from(main_pos)
353 })?;
354
355 Ok(scale)
356 }
357
358 fn set_source_position(&self, position: Vec2) -> Result<(), ObsError> {
360 let self_ptr = self.__native_handle().clone();
361
362 run_with_obs!(self.runtime(), (self_ptr), move || {
363 let position: libobs::vec2 = position.into();
364
365 unsafe {
366 libobs::obs_sceneitem_set_pos(self_ptr.get_ptr(), &position);
368 }
369 })?;
370
371 Ok(())
372 }
373
374 fn set_transform_info(&self, info: &ObsTransformInfo) -> Result<(), ObsError> {
377 let item_info = Sendable(info.clone());
378 let self_ptr = self.__native_handle().clone();
379
380 run_with_obs!(self.runtime(), (self_ptr, item_info), move || {
381 let item_info = item_info.0 .0;
382
383 unsafe {
384 libobs::obs_sceneitem_set_info2(self_ptr.get_ptr(), &item_info);
386 }
387 })?;
388
389 Ok(())
390 }
391
392 fn fit_source_to_screen(&self) -> Result<bool, ObsError> {
397 let self_ptr = self.__native_handle().clone();
398 let is_locked = {
399 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
400 libobs::obs_sceneitem_locked(self_ptr.get_ptr())
402 })?
403 };
404
405 if is_locked {
406 return Ok(false);
407 }
408
409 let ovi = run_with_obs!(self.runtime(), (), move || {
410 let mut ovi = std::mem::MaybeUninit::<obs_video_info>::uninit();
411 let success = unsafe {
412 libobs::obs_get_video_info(ovi.as_mut_ptr())
414 };
415
416 if success {
417 let res = unsafe {
418 ovi.assume_init()
420 };
421
422 Ok(Sendable(res))
423 } else {
424 Err(ObsError::NullPointer(Some(
425 "Failed to get video info".to_string(),
426 )))
427 }
428 })??;
429
430 let bounds_crop = run_with_obs!(self.runtime(), (self_ptr), move || {
431 unsafe {
432 libobs::obs_sceneitem_get_bounds_crop(self_ptr.get_ptr())
434 }
435 })?;
436
437 let item_info = ObsTransformInfoBuilder::new()
439 .set_bounds_type(ObsBoundsType::ScaleInner)
440 .set_crop_to_bounds(bounds_crop)
441 .build(ovi.0.base_width, ovi.0.base_height);
442
443 self.set_transform_info(&item_info)?;
444 Ok(true)
445 }
446
447 fn is_visible(&self) -> Result<bool, ObsError> {
449 let self_ptr = self.__native_handle().clone();
450 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
451 libobs::obs_sceneitem_visible(self_ptr.get_ptr())
453 })
454 }
455
456 fn crop(&self) -> Result<ObsSceneItemCrop, ObsError> {
458 let self_ptr = self.__native_handle().clone();
459 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
460 let mut crop = libobs::obs_sceneitem_crop {
463 left: 0,
464 top: 0,
465 right: 0,
466 bottom: 0,
467 };
468 libobs::obs_sceneitem_get_crop(self_ptr.get_ptr(), &mut crop);
469 ObsSceneItemCrop::from(crop)
470 })
471 }
472
473 fn set_crop(&self, crop: ObsSceneItemCrop) -> Result<(), ObsError> {
476 let self_ptr = self.__native_handle().clone();
477 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
478 let crop: libobs::obs_sceneitem_crop = crop.into();
481 libobs::obs_sceneitem_set_crop(self_ptr.get_ptr(), &crop);
482 })
483 }
484
485 fn set_visible(&self, visible: bool) -> Result<(), ObsError> {
487 let self_ptr = self.__native_handle().clone();
488 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
489 let _ = libobs::obs_sceneitem_set_visible(self_ptr.get_ptr(), visible);
491 })
492 }
493
494 fn is_locked(&self) -> Result<bool, ObsError> {
496 let self_ptr = self.__native_handle().clone();
497 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
498 libobs::obs_sceneitem_locked(self_ptr.get_ptr())
500 })
501 }
502
503 fn set_locked(&self, locked: bool) -> Result<(), ObsError> {
505 let self_ptr = self.__native_handle().clone();
506 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
507 let _ = libobs::obs_sceneitem_set_locked(self_ptr.get_ptr(), locked);
509 })
510 }
511
512 fn rotation(&self) -> Result<f32, ObsError> {
514 let self_ptr = self.__native_handle().clone();
515 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
516 libobs::obs_sceneitem_get_rot(self_ptr.get_ptr())
518 })
519 }
520
521 fn set_rotation(&self, degrees: f32) -> Result<(), ObsError> {
523 let self_ptr = self.__native_handle().clone();
524 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
525 libobs::obs_sceneitem_set_rot(self_ptr.get_ptr(), degrees);
527 })
528 }
529
530 fn order_position(&self) -> Result<i32, ObsError> {
532 let self_ptr = self.__native_handle().clone();
533 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
534 libobs::obs_sceneitem_get_order_position(self_ptr.get_ptr())
536 })
537 }
538
539 fn set_order_position(&self, position: i32) -> Result<(), ObsError> {
541 if position < 0 {
542 return Err(ObsError::InvalidOperation(
543 "Scene item order position cannot be negative".to_string(),
544 ));
545 }
546 let self_ptr = self.__native_handle().clone();
547 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
548 libobs::obs_sceneitem_set_order_position(self_ptr.get_ptr(), position);
550 })
551 }
552
553 fn move_order(&self, movement: ObsOrderMovement) -> Result<(), ObsError> {
555 let self_ptr = self.__native_handle().clone();
556 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
557 libobs::obs_sceneitem_set_order(self_ptr.get_ptr(), movement as libobs::obs_order_movement);
559 })
560 }
561
562 fn scale_filter(&self) -> Result<ObsScaleType, ObsError> {
564 let self_ptr = self.__native_handle().clone();
565 let raw = run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
566 libobs::obs_sceneitem_get_scale_filter(self_ptr.get_ptr())
568 })?;
569 ObsScaleType::from_i64(raw as i64).ok_or_else(|| {
570 ObsError::EnumConversionError(format!("unknown scene-item scale filter {raw}"))
571 })
572 }
573
574 fn set_scale_filter(&self, filter: ObsScaleType) -> Result<(), ObsError> {
575 let self_ptr = self.__native_handle().clone();
576 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
577 libobs::obs_sceneitem_set_scale_filter(
579 self_ptr.get_ptr(),
580 filter as libobs::obs_scale_type,
581 );
582 })
583 }
584
585 fn blend_method(&self) -> Result<ObsBlendMethod, ObsError> {
586 let self_ptr = self.__native_handle().clone();
587 let raw = run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
588 libobs::obs_sceneitem_get_blending_method(self_ptr.get_ptr())
590 })?;
591 ObsBlendMethod::from_i64(raw as i64).ok_or_else(|| {
592 ObsError::EnumConversionError(format!("unknown scene-item blend method {raw}"))
593 })
594 }
595
596 fn set_blend_method(&self, method: ObsBlendMethod) -> Result<(), ObsError> {
597 let self_ptr = self.__native_handle().clone();
598 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
599 libobs::obs_sceneitem_set_blending_method(
601 self_ptr.get_ptr(),
602 method as libobs::obs_blending_method,
603 );
604 })
605 }
606
607 fn blend_mode(&self) -> Result<ObsBlendMode, ObsError> {
608 let self_ptr = self.__native_handle().clone();
609 let raw = run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
610 libobs::obs_sceneitem_get_blending_mode(self_ptr.get_ptr())
612 })?;
613 ObsBlendMode::from_i64(raw as i64).ok_or_else(|| {
614 ObsError::EnumConversionError(format!("unknown scene-item blend mode {raw}"))
615 })
616 }
617
618 fn set_blend_mode(&self, mode: ObsBlendMode) -> Result<(), ObsError> {
619 let self_ptr = self.__native_handle().clone();
620 run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
621 libobs::obs_sceneitem_set_blending_mode(
623 self_ptr.get_ptr(),
624 mode as libobs::obs_blending_type,
625 );
626 })
627 }
628
629 fn transform_snapshot(&self) -> Result<ObsSceneItemTransformSnapshot, ObsError> {
631 let info = self.get_transform_info()?;
632 let bounds_type = info.get_bounds_type().ok_or_else(|| {
633 ObsError::EnumConversionError("unknown scene-item bounds type".into())
634 })?;
635 Ok(ObsSceneItemTransformSnapshot {
636 position: info.get_pos(),
637 scale: info.get_scale(),
638 alignment: ObsAlignment::from_bits_retain(info.get_alignment()),
639 rotation: info.get_rot(),
640 bounds: info.get_bounds(),
641 bounds_type,
642 bounds_alignment: ObsAlignment::from_bits_retain(info.get_bounds_alignment()),
643 crop_to_bounds: info.get_crop_to_bounds(),
644 crop: self.crop()?,
645 scale_filter: self.scale_filter()?,
646 blend_method: self.blend_method()?,
647 blend_mode: self.blend_mode()?,
648 })
649 }
650
651 fn apply_transform_snapshot(
653 &self,
654 snapshot: &ObsSceneItemTransformSnapshot,
655 ) -> Result<(), ObsError> {
656 let info = ObsTransformInfoBuilder::new()
657 .set_pos(snapshot.position)
658 .set_scale(snapshot.scale)
659 .set_alignment(snapshot.alignment)
660 .set_rot(snapshot.rotation)
661 .set_bounds(snapshot.bounds)
662 .set_bounds_type(snapshot.bounds_type)
663 .set_bounds_alignment(snapshot.bounds_alignment)
664 .set_crop_to_bounds(snapshot.crop_to_bounds)
665 .build_with_fallback(self)?;
666 self.set_transform_info(&info)?;
667 self.set_crop(snapshot.crop)?;
668 self.set_scale_filter(snapshot.scale_filter)?;
669 self.set_blend_method(snapshot.blend_method)?;
670 self.set_blend_mode(snapshot.blend_mode)
671 }
672
673 fn state_snapshot(&self) -> Result<ObsSceneItemStateSnapshot, ObsError> {
674 Ok(ObsSceneItemStateSnapshot {
675 transform: self.transform_snapshot()?,
676 visible: self.is_visible()?,
677 locked: self.is_locked()?,
678 order_position: self.order_position()?,
679 })
680 }
681
682 fn apply_state_snapshot(
683 &self,
684 snapshot: &ObsSceneItemStateSnapshot,
685 ) -> Result<(), ObsError> {
686 self.apply_transform_snapshot(&snapshot.transform)?;
687 self.set_visible(snapshot.visible)?;
688 self.set_locked(snapshot.locked)?;
689 self.set_order_position(snapshot.order_position)
690 }
691
692 fn position(&self) -> Result<Vec2, ObsError> {
694 self.get_source_position()
695 }
696
697 fn set_position(&self, position: Vec2) -> Result<(), ObsError> {
699 self.set_source_position(position)
700 }
701
702 fn scale(&self) -> Result<Vec2, ObsError> {
704 self.get_source_scale()
705 }
706
707 fn set_scale(&self, scale: Vec2) -> Result<(), ObsError> {
709 self.set_source_scale(scale)
710 }
711
712 fn set_source_scale(&self, scale: Vec2) -> Result<(), ObsError> {
714 let self_ptr = self.__native_handle().clone();
715
716 run_with_obs!(self.runtime(), (self_ptr), move || {
717 let scale: libobs::vec2 = scale.into();
718
719 unsafe {
720 libobs::obs_sceneitem_set_scale(self_ptr.get_ptr(), &scale);
722 }
723 })?;
724
725 Ok(())
726 }
727 }
728}
729
730impl SceneItemTraitSealed for ObsSceneItemHandle {
731 fn __native_handle(&self) -> &SmartPointerSendable<*mut obs_scene_item> {
732 &self.scene_item_ptr
733 }
734
735 fn __removed_flag(&self) -> &Arc<Mutex<bool>> {
736 &self.removed
737 }
738}
739
740impl SceneItemTrait for ObsSceneItemHandle {
741 fn runtime(&self) -> ObsRuntime {
742 self.runtime.clone()
743 }
744}
745
746impl<T: ObsSourceTrait + Clone> SceneItemTraitSealed for ObsSceneItemRef<T> {
747 fn __native_handle(&self) -> &SmartPointerSendable<*mut obs_scene_item> {
748 &self.scene_item_ptr
749 }
750
751 fn __removed_flag(&self) -> &Arc<Mutex<bool>> {
752 &self.removed
753 }
754}
755
756impl<T: ObsSourceTrait + Clone> SceneItemTrait for ObsSceneItemRef<T> {
757 fn runtime(&self) -> ObsRuntime {
758 self.runtime.clone()
759 }
760}
761
762impl<T> ObsSceneItemRef<T>
763where
764 T: ObsSourceTrait + Clone,
765{
766 pub fn inner_source(&self) -> &T {
768 &self.underlying_source
769 }
770
771 pub fn inner_source_mut(&mut self) -> &mut T {
773 &mut self.underlying_source
774 }
775}
776
777impl<T: ObsSourceTrait + Clone> PartialEq for ObsSceneItemRef<T> {
781 fn eq(&self, other: &Self) -> bool {
782 self.scene_item_ptr.native_id() == other.scene_item_ptr.native_id()
783 }
784}
785
786impl<T: ObsSourceTrait + Clone> Eq for ObsSceneItemRef<T> {}
787
788impl<T: ObsSourceTrait + Clone> Hash for ObsSceneItemRef<T> {
789 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
790 self.scene_item_ptr.native_id().hash(state);
791 }
792}
793
794#[cfg(test)]
795mod crop_tests {
796 use super::ObsSceneItemCrop;
797
798 #[test]
799 fn obs_scene_item_crop_round_trips_native_layout() {
800 let crop = ObsSceneItemCrop::new(1, 2, 3, 4);
801 let native: libobs::obs_sceneitem_crop = crop.into();
802 assert_eq!(native.left, 1);
803 assert_eq!(native.top, 2);
804 assert_eq!(native.right, 3);
805 assert_eq!(native.bottom, 4);
806 assert_eq!(ObsSceneItemCrop::from(native), crop);
807 }
808
809 #[test]
810 fn obs_scene_item_crop_default_is_uncropped() {
811 assert_eq!(
812 ObsSceneItemCrop::default(),
813 ObsSceneItemCrop::new(0, 0, 0, 0)
814 );
815 }
816}